/*
==============================================================================
midiMonitor

some code borrowed from Jost midi monitor:
http://www.anticore.org/jucetice/

==============================================================================
*/
#include "MidiMonitor.h"
#include "MidiMonitorEditor.h"


//==============================================================================
/**
    This function must be implemented to create the actual plugin object that
    you want to use.
*/
PizAudioProcessor* JUCE_CALLTYPE createPluginFilter()
{
    return new MidiMonitorPlugin();
}

//==============================================================================
MidiMonitorPlugin::MidiMonitorPlugin()
: loop(0), messages(0)
{
	iter = 0;
    lastUIWidth = 392;
    lastUIHeight = 250;
    loop = new MidiMessageSequence();
	messages = new MidiMessageSequence();

    useNotes=true;
    useCC=true;
    usePB=true;
    usePA=true;
    useCP=true;
    usePC=true;
    useSysEx=true;
    useOther=true;
    useClock=true;

    waitingForFirstMessage=true;

    bghue=1.f;//0.61f;
    bgsat=0.f;//0.52f;
    bgbri=1.f;//0.11f;
    contrast=1.0f;
    power=1.0f;
    thru=1.0f;
    bytes=1.0f;
	wrap=0.f;
	showtime=0.f;
	ticks=0.f;
	frames=0.f;
	mode=0.f;
	maxLines=5000;

	programName = "midiMonitor " + String(JucePlugin_VersionString);

	if (!loadDefaultFxb())
		loadDefaultFxp();
}

MidiMonitorPlugin::~MidiMonitorPlugin()
{
	deleteAndZero(loop);
	deleteAndZero(messages);
}

//==============================================================================
int MidiMonitorPlugin::getNumParameters()
{
    return kNumParams;
}

float MidiMonitorPlugin::getParameter (int index)
{
    switch (index) 
	{
    case kBgHue:	return bghue; break;
    case kBgSat:	return bgsat; break;
    case kBgBri:	return bgbri; break;
    case kContrast: return contrast; break;
    case kPower:	return power; break;
	case kThru:		return thru; break;
    case kBytes:	return bytes; break;
    case kWrap:		return wrap; break;
	case kTime:		return showtime; break;
	case kTicks:	return ticks; break;
	case kTimeMode:	return mode; break;
	case kShowms:   return 1.f; break;
	case kShowhrs:  return 1.f; break;
	case kShowsubfr: return 1.f; break;
	case kFrames:   return frames; break;

    default: return 0.0f; break;
    }
}

void MidiMonitorPlugin::setParameter (int index, float newValue)
{
    switch (index) {
        case kBgHue:	bghue = newValue; break;
        case kBgSat:	bgsat = newValue; break;
        case kBgBri:	bgbri = newValue; break;
        case kContrast: contrast = newValue; break;
        case kPower:	power = newValue; break;
        case kThru:		thru = newValue; break;
        case kBytes:	bytes = newValue; break;
        case kWrap:		wrap = newValue; break;
		case kTime:		showtime = newValue; break;
		case kTicks:	ticks = newValue; break;
		case kTimeMode:	mode = newValue; break;
		case kFrames:   frames = newValue; break;
        default: break;
    }
    sendChangeMessage ();
}

const String MidiMonitorPlugin::getParameterName (int index)
{
    switch (index) {
        case kBgHue:	return String("Hue"); break;
        case kBgSat:	return String("Saturation"); break;
        case kBgBri:	return String("Brightness"); break;
        case kContrast: return String("Contrast"); break;
        case kPower:	return String("Power"); break;
        case kThru:		return String("Thru"); break;
        case kBytes:	return String("ShowBytes"); break;
        case kWrap:		return String("WordWrap"); break;
		case kTime:		return String("ShowTime"); break;
		case kTicks:	return String("TicksPerBeat"); break;
		case kTimeMode:	return String("TimeMode"); break;
		case kFrames:	return String("FramesPerSec"); break;
        default: return String::empty; break;
    }
}

const String MidiMonitorPlugin::getParameterText (int index)
{
    switch (index) {
        case kBgHue:	return String(roundFloatToInt(100.f*bghue)); break;
        case kBgSat:	return String(roundFloatToInt(100.f*bgsat)); break;
        case kBgBri:	return String(roundFloatToInt(100.f*bgbri)); break;
        case kContrast: return String(roundFloatToInt(100.f*contrast)); break;
        case kPower:	return String(power); break;
        case kThru:		return String(thru); break;
        case kBytes:	return String(bytes); break;
        case kWrap:		return String(wrap); break;
		case kTime:		return String(showtime); break;
		case kTicks:	return String(ticks); break;
		case kTimeMode:	return String(mode); break;
		case kFrames:	return String(frames); break;
		default: return String::empty; break;
    }
    
}
const String MidiMonitorPlugin::getInputChannelName (const int channelIndex) const
{
    return String (channelIndex + 1);
}

const String MidiMonitorPlugin::getOutputChannelName (const int channelIndex) const
{
    return String (channelIndex + 1);
}

bool MidiMonitorPlugin::isInputChannelStereoPair (int index) const
{
    return true;
}

bool MidiMonitorPlugin::isOutputChannelStereoPair (int index) const
{
    return true;
}



//==============================================================================
void MidiMonitorPlugin::prepareToPlay (double sampleRate, int samplesPerBlock)
{
    midiCollector.reset (sampleRate);
}

void MidiMonitorPlugin::releaseResources()
{
    midiCollector.reset (44100);
}

void MidiMonitorPlugin::processBlock (AudioSampleBuffer& buffer,
                                   MidiBuffer& midiMessages)
{
	++iter;
    for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i)
    {
        buffer.clear (i, 0, buffer.getNumSamples());
    }
    if (power>=0.5f) {
        AudioPlayHead::CurrentPositionInfo pos;
        if (getPlayHead() != 0 && getPlayHead()->getCurrentPosition (pos)) {
            if (memcmp (&pos, &lastPosInfo, sizeof (pos)) != 0) {
                lastPosInfo = pos;
            }
        }
        else { //no timeinfo
            zeromem (&lastPosInfo, sizeof (lastPosInfo));
            lastPosInfo.timeSigNumerator = 4;
            lastPosInfo.timeSigDenominator = 4;
            lastPosInfo.bpm = 120;
            lastPosInfo.ppqPosition = samples*(120/(60*getSampleRate()));
        }
        //if (param[kSync]>=0.5f) { //sample sync
        //    lastPosInfo.ppqPosition = samples*(lastPosInfo.bpm/(60*getSampleRate()));
        //    lastPosInfo.isPlaying = true;
        //}
        const double ppqPerSample = lastPosInfo.bpm/(60.0*getSampleRate());
        //const bool playing = lastPosInfo.isPlaying || lastPosInfo.isRecording;
		
		n = lastPosInfo.timeSigNumerator;
		d = lastPosInfo.timeSigDenominator;
        int samplePos = 0;
        MidiMessage message (0xf4, 0.0);
        MidiBuffer::Iterator eventIterator (midiMessages);

        double counter = Time::getMillisecondCounterHiRes();
        while (eventIterator.getNextEvent (message, samplePos))
        {
            bool use=false;
            if ((message.isNoteOnOrOff() && useNotes)
                || (message.isController() && useCC)
                || (message.isPitchWheel() && usePB)
                || (message.isProgramChange() && usePC)
                || (message.isAftertouch() && usePA)
                || (message.isChannelPressure() && useCP) 
                || (message.isMidiClock() && useClock) 
                || (message.isSysEx() && useSysEx)
                || ( (message.isActiveSense()
                    || message.isMetaEvent()
                    || message.isMidiContinue()
                    || message.isMidiStart()
                    || message.isMidiStop()
                    || message.isMidiMachineControlMessage()
                    || message.isQuarterFrame()
                    || message.isSongPositionPointer()
                    ) && useOther ) 
                ) {
                    if (waitingForFirstMessage) {
                        samples=0;
                        waitingForFirstMessage=false;
                    }
                    //message.setTimeStamp(counter);
                    //midiCollector.addMessageToQueue (message);

                    message.setTimeStamp(0);
					if (getActiveEditor() != 0)
					{
						if (getParameter(kTimeMode)==1.f)
							messages->addEvent(message,(double)iter + (double)samplePos*0.000001);
						else 
							messages->addEvent(message,lastPosInfo.ppqPosition + ((double)samplePos)*ppqPerSample);
					}
					loop->addEvent(message,roundDoubleToInt((samples+(double)samplePos)*ppqPerSample*960.0));
                    if (message.isNoteOnOrOff()) {
                        loop->updateMatchedPairs();
                    }
            }
        }
    }
    if (thru<0.5f) midiMessages.clear();
    samples+=buffer.getNumSamples();
}

//==============================================================================
AudioProcessorEditor* MidiMonitorPlugin::createEditor()
{
    return new MidiMonitorEditor (this);
}

//==============================================================================
void MidiMonitorPlugin::getStateInformation (MemoryBlock& destData)
{
    // you can store your parameters as binary data if you want to or if you've got
    // a load of binary to put in there, but if you're not doing anything too heavy,
    // XML is a much cleaner way of doing it - here's an example of how to store your
    // params as XML..

    // create an outer XML element..
    XmlElement xmlState ("MYPLUGINSETTINGS");

    // add some attributes to it..
    xmlState.setAttribute ("pluginVersion", 1);
    xmlState.setAttribute ("Hue", bghue);
    xmlState.setAttribute ("Sat", bgsat);
    xmlState.setAttribute ("Bri", bgbri);
    xmlState.setAttribute ("Contrast", contrast);
    xmlState.setAttribute ("Power", power);
    xmlState.setAttribute ("Thru", thru);
    xmlState.setAttribute ("ShowBytes", bytes);
    xmlState.setAttribute ("WordWrap", wrap);
	xmlState.setAttribute ("ShowTime", showtime);
	xmlState.setAttribute ("TimeMode", mode);
	xmlState.setAttribute ("TicksPerBeat", ticks);
	xmlState.setAttribute ("FramesPerSec", frames);
	xmlState.setAttribute ("MaxLines", maxLines);

    xmlState.setAttribute ("useNotes",useNotes);
    xmlState.setAttribute ("useCC",useCC);
    xmlState.setAttribute ("usePB",usePB);
    xmlState.setAttribute ("usePC",usePC);
    xmlState.setAttribute ("useCP",useCP);
    xmlState.setAttribute ("usePA",usePA);
    xmlState.setAttribute ("useClock",useClock);
    xmlState.setAttribute ("useSysEx",useSysEx);
    xmlState.setAttribute ("useOther",useOther);

    xmlState.setAttribute ("uiWidth", lastUIWidth);
    xmlState.setAttribute ("uiHeight", lastUIHeight);

    // you could also add as many child elements as you need to here..


    // then use this helper function to stuff it into the binary blob and return it..
    copyXmlToBinary (xmlState, destData);
}

void MidiMonitorPlugin::setStateInformation (const void* data, int sizeInBytes)
{
    // use this helper function to get the XML from this binary blob..
    XmlElement* const xmlState = getXmlFromBinary (data, sizeInBytes);

    if (xmlState != 0)
    {
        // check that it's the right type of xml..
        if (xmlState->hasTagName ("MYPLUGINSETTINGS"))
        {
            // ok, now pull out our parameters..
            bghue = (float) xmlState->getDoubleAttribute ("Hue", bghue);
            bgsat = (float) xmlState->getDoubleAttribute ("Sat", bgsat);
            bgbri = (float) xmlState->getDoubleAttribute ("Bri", bgbri);
            contrast = (float) xmlState->getDoubleAttribute ("Contrast", contrast);
            power = (float) xmlState->getDoubleAttribute ("Power", power);
            thru = (float) xmlState->getDoubleAttribute ("Thru", thru);
            bytes = (float) xmlState->getDoubleAttribute ("ShowBytes", bytes);
            wrap = (float) xmlState->getDoubleAttribute ("WordWrap", wrap);
			showtime = (float) xmlState->getDoubleAttribute ("ShowTime", showtime);
			mode = (float) xmlState->getDoubleAttribute ("TimeMode", mode);
			ticks = (float) xmlState->getDoubleAttribute ("TicksPerBeat", ticks);
			frames = (float) xmlState->getDoubleAttribute ("FramesPerSec", frames);
			maxLines = xmlState->getIntAttribute ("MaxLines", maxLines);

            useNotes = xmlState->getBoolAttribute ("useNotes", true);
            useCC = xmlState->getBoolAttribute ("useCC", true);
            usePB = xmlState->getBoolAttribute ("usePB", true);
            usePC = xmlState->getBoolAttribute ("usePC", true);
            useCP = xmlState->getBoolAttribute ("useCP", true);
            usePA = xmlState->getBoolAttribute ("usePA", true);
            useClock = xmlState->getBoolAttribute ("useClock", true);
            useSysEx = xmlState->getBoolAttribute ("useSysEx", true);
            useOther = xmlState->getBoolAttribute ("useOther", true);

            lastUIWidth = xmlState->getIntAttribute ("uiWidth", lastUIWidth);
            lastUIHeight = xmlState->getIntAttribute ("uiHeight", lastUIHeight);

            sendChangeMessage ();
        }

        delete xmlState;
    }
    this->dispatchPendingMessages();
}

bool MidiMonitorPlugin::writeMidiFile(File mid) {
    MidiFile midifile;
    midifile.setTicksPerQuarterNote(960);
    MidiMessageSequence metadata;

    MidiMessage nstart = MidiMessage(0x9f,62,1,0);
    metadata.addEvent(nstart,0.0);

    uint8 tn [] = {0xFF,0x03,11,'m','i','d','i','M','o','n','i','t','o','r'};
    MidiMessage trackname = MidiMessage(tn,14,0);
    loop->addEvent(trackname);

    midifile.addTrack(*loop);

    if (mid.exists()) mid.deleteFile();
    if (mid.create()) {
        FileOutputStream file(mid);
        midifile.writeTo(file);
    }
    return true;
}

void MidiMonitorPlugin::clearLoop() {
    loop->clear();
    samples=0;
    waitingForFirstMessage=true;
};
